package com.kjs.web.jwt.filter;

import com.alibaba.fastjson.JSONObject;
import com.kjs.common.consts.RedisCatalogConst;
import com.kjs.common.resultModel.JsonWebToken;
import com.kjs.common.consts.ResultConst;
import com.kjs.common.resultModel.JwtUser;
import com.kjs.common.tools.*;
import com.kjs.web.cacheService.IJwtUserCacheService;
import com.kjs.web.jwt.wrapper.ParameterRequestWrapper;
import com.kjs.common.exception.JwtAuthenFailureException;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


/**
 * jwt的安全认证
 * jwt的claim中包含有用户的登录id：通过本id可以从缓存中获取到登录的用户信息
 */
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Value("${jwt.auth.head:Bearer}")
    private String jwtAuthHead; // 认证的头部信息
    @Value("${sign.head:kjs}")
    private String signHead; // 签名的头部名称
    @Value("${jwt.cert}")
    private String cert;
    @Value("${security.ant.permit}")
    private String[] permitUrls;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private IJwtUserCacheService jwtUserCacheService;


    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String uriPath = request.getServletPath();

        // 一旦配置了该过滤器，该过滤器发生在：WebSecurityConfigurerAdapter中configure设置的
        // authorizeRequests.antMatcher之前，所以如果验证授权信息失败，需要调用默认的过滤链
        if (permitUrls != null && permitUrls.length > 0) {
            for (String permitUrl : permitUrls) {
                if (uriPath.equals(permitUrl)) {
                    filterChain.doFilter(request, response);
                    return;
                }
            }
        }
        // 打开swagger2
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        JsonWebToken token;
        try {
            token = checkAuthorization(request);
        } catch (ServletException | UsernameNotFoundException e) {
            // 此处需要调用默认的过滤链
            // 不能直接抛出异常
            if (e instanceof JwtAuthenFailureException) {
                response.setHeader("err", e.getMessage());
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
            filterChain.doFilter(request, response);
            return;
        }

        if (!validateSignature(request, token)) {
            filterChain.doFilter(request, response);
            return;
        }
        String ip = request.getRemoteAddr();
        token.setIp(ip);

        request = filter(request, token);

        filterChain.doFilter(request, response);
    }

    private HttpServletRequest filter(HttpServletRequest request, JsonWebToken token) {
        Map<String, String[]> params = new HashMap<>(request.getParameterMap());
        params.put("jwt", new String[]{JSONObject.toJSONString(token)});
        request = new ParameterRequestWrapper(request, params);
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(token.getUser(), null, AuthorityUtils.createAuthorityList("ROLE_USER"));
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return request;
    }

    private boolean validateSignature(HttpServletRequest request, JsonWebToken token) throws ServletException {
//        String uriPath = request.getRequestURI();
//        String kjsHeader = request.getHeader(signHead);
//        if (StringChecker.isEmpty(kjsHeader)) {
//            log.error("{}: 缺少接口签名信息：{}", uriPath, signHead);
//            return false;
//            //throw new LackOfSignatureException();
//        }
//        // 格式：v_timestamp_code
//        // v：接口版本
//        // timestamp：请求的时间戳
//        // code：签名结果
//        String headers[] = kjsHeader.split("_");
//        // 签名算法：md5(jwtToken+timestamp+v+salt+appKey)
//        if (headers.length < 3) {
//            log.error("{}: 签名属性格式不正确: {}", uriPath, kjsHeader);
//            return false;
//        }
//        String str = token.getJwt() + headers[1] + headers[0] + token.getUser().getSalt() + cert;
//        String md5 = MD.md5(str);
//
//        // 提取code
//        String code = CodeUtil.code(md5);
//        if (!code.equals(headers[2])) {
//            log.error("{}: 签名不一致：{} - {}", uriPath, code, headers[2]);
//            return false;
//            //throw new SignatureMismatchException(request.getContextPath(), code, headers[2]);
//        }
        return true;
    }

    /**
     * 认证信息头部
     *
     * @param request
     * @return
     * @throws ServletException
     */
    private JsonWebToken checkAuthorization(HttpServletRequest request) throws ServletException {
        JsonWebToken token = new JsonWebToken();
        String uriPath = request.getRequestURI();
        String jwtHeader = request.getHeader("Authorization");
        // jwtHeader的格式: Bearer appId access_token，中间用空格分割
        if (StringChecker.isEmpty(jwtHeader)) {
            log.warn("{}: 缺少头部认证信息：Authorization", uriPath);
            throw new JwtAuthenFailureException(ResultConst.ERR_AUTH_LACK_OF);
        }
        String jwtHeaders[] = jwtHeader.split(" ");
        if (jwtHeaders.length < 2) {
            log.error("{}: 头部认证信息：Authorization的格式不正确，字段不足: {}", uriPath, jwtHeader);
            throw new JwtAuthenFailureException(ResultConst.ERR_AUTH_FORMAT);
        }
        if (!jwtHeaders[0].equals(jwtAuthHead)) {
            log.warn("{}: 头部认证信息：Authorization的认证方式不是JWT模式", uriPath);
            throw new JwtAuthenFailureException(ResultConst.ERR_AUTH_UNSUPPORTED);
        }
        String authToken = jwtHeaders[1];
        if (StringChecker.isEmpty(authToken)) {
            log.error("{}: 头部认证信息：Authorization缺少jwt的token", uriPath);
            throw new JwtAuthenFailureException(ResultConst.ERR_AUTH_JWT_EMPTY);
        }
        Claims claims = JwtTokenUtil.getClaimsFromToken(cert, authToken);

        if (claims == null) {
            log.error("{}: 头部认证信息：Authorization的token签名失败", uriPath);
            throw new JwtAuthenFailureException(ResultConst.ERR_AUTH_JWT_SIGN);
        }
        if (JwtTokenUtil.isTokenExpired(claims)) {
            log.error("{}: 头部认证信息：Authorization的token已经失效", uriPath);
            throw new JwtAuthenFailureException(ResultConst.ERR_AUTH_JWT_EXPIRE);
        }
        // 获取认证的ID
        String authId = JwtTokenUtil.getAuthIdFromToken(claims);
//        Integer id = (Integer) redisUtil.get(RedisCatalogConst.TOKEN + ":" + authId);
//        if (id == null) {
//            throw new UsernameNotFoundException(authId);
//        }
//        JwtUser jwtUser = jwtUserCacheService.findJwtUserById(id);
        token.setAuthId(authId);
        token.setJwt(authToken);
//        token.setUser(jwtUser);
        return token;
    }
}
